
package com.hlkj.pay.util;
/*
 * Hlpay-Plus aggregate payment system.
 * Copyright (c) 2024-2025 Hlpay Team Copyright has the right of final interpretation.
 */

import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

import org.bouncycastle.util.encoders.Base64;
import org.springframework.util.StringUtils;

import com.hlkj.framework.common.util.json.JsonUtils;
import com.hlkj.pay.app.merchant.ext.impl.fuyou.constant.FuYouConstant;

import lombok.extern.slf4j.Slf4j;

/**
 *
 * @author HlpayTeam
 * @date 2024/10/09 13:16
 */
@Slf4j
public class SignUtils {

    public static final String charset = "utf-8";

    /**
     * RSA2对应的真实非对称加密算法名称
     */
    public static final String RSA = "RSA";

    /**
     * RSA2对应的真实签名算法名称
     */
    public static final String SHA_256_WITH_RSA = "SHA256WithRSA";

    /**
     * 通用签名方法
     *
     * @param params
     *            待签名内容
     * @param privateKey
     *            私钥
     * @param signType
     *            签名类型：RSA2
     * @return
     * @throws Exception
     */
    public static String signRsa(Map<String, Object> params, String privateKey, String signType) throws Exception {
        String signContent = getSignContent(params);
        log.info("signRsa signContent:{}", signContent);
        return generateSign(signContent, privateKey);
    }

    /**
     * 通用签名方法
     *
     * @param params
     *            待签名内容
     * @param privateKey
     *            私钥
     * @throws Exception
     */
    public static String signMd5(Map<String, Object> params, String privateKey) throws Exception {
        // params.put("apikey",privateKey);
        String signContent = getSignCheckContent(params);
        signContent = signContent + "&apikey=" + privateKey;
        log.info("verify signContent:{}", signContent);
        return md5(signContent);
    }

    /**
     * 通用签名方法
     *
     * @param params
     *            待签名内容
     * @param privateKey
     *            私钥
     * @return
     * @throws Exception
     */
    public static String signIncludeNullMd5(Map<String, Object> params, String privateKey) throws Exception {
        // params.put("apikey",privateKey);
        String signContent = getSignCheckContentIncludeNull(params);
        signContent = signContent + "&apikey=" + privateKey;
        log.info("verify signContent:{}", signContent);
        return md5(signContent);
    }

    /**
     * @param sortedParams
     * @return
     */
    public static String getSignContent(Map<String, Object> sortedParams) {
        StringBuilder content = new StringBuilder();
        List<String> keys = new ArrayList<String>(sortedParams.keySet());
        Collections.sort(keys);
        int index = 0;
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            Object value = sortedParams.get(key);
            if (value == null || !StringUtils.hasText(value.toString())) {
                continue;
            }
            if (!baseType(value)) {
                content.append(index == 0 ? "" : "&").append(key).append("=").append(sort(value));
            }
            else {
                content.append(index == 0 ? "" : "&").append(key).append("=").append(value.toString());
            }
            index++;
        }
        return content.toString();
    }

    public static String sort(Object value) {
        Map<String, Object> map = JsonUtils.parseObject(JsonUtils.toJsonStringNotNull(value), Map.class);
        TreeMap<String, Object> treeMap = new TreeMap<>();
        map.keySet().forEach((key) -> {
            if (map.get(key) != null && StringUtils.hasText(map.get(key).toString())) {
                treeMap.put(key, map.get(key));
            }
        });
        return JsonUtils.toJsonStringNotNull(treeMap);
    }

    public static boolean baseType(Object arg) {
        return arg instanceof Integer || arg instanceof String || arg instanceof Long || arg instanceof Double || arg instanceof Float || arg instanceof Byte
                || arg instanceof Short || arg instanceof Character || arg instanceof Boolean;
    }

    /**
     * @param sortedParams
     * @return
     */
    public static String getSignCheckContent(Map<String, Object> sortedParams) {
        if (sortedParams == null) {
            return null;
        }
        sortedParams.remove("sign");
        StringBuilder content = new StringBuilder();
        List<String> keys = new ArrayList<String>(sortedParams.keySet());
        Collections.sort(keys);
        int index = 0;
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            Object value = sortedParams.get(key);
            if (value == null || !StringUtils.hasText(value.toString())) {
                continue;
            }
            if (!baseType(value)) {
                content.append(index == 0 ? "" : "&").append(key).append("=").append(sort(value));
            }
            else {
                content.append(index == 0 ? "" : "&").append(key).append("=").append(value.toString());
            }
            index++;
        }
        return content.toString();
    }

    /**
     * @param sortedParams
     * @return
     */
    public static String getSignCheckContentIncludeNull(Map<String, Object> sortedParams) {
        if (sortedParams == null) {
            return null;
        }
        sortedParams.remove("sign");
        StringBuilder content = new StringBuilder();
        List<String> keys = new ArrayList<String>(sortedParams.keySet());
        Collections.sort(keys);
        int index = 0;
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            Object value = sortedParams.get(key);
            if (value == null || !StringUtils.hasText(value.toString())) {
                value = "";
            }
            content.append(index == 0 ? "" : "&").append(key).append("=").append(value.toString());
            index++;
        }
        return content.toString();
    }

    /**
     * 密钥模式RSA2通用验签方法，主要用于支付接口的返回参数的验签比如：当面付，APP支付，手机网站支付，电脑网站支付
     *
     * @param params
     *            待验签的从支付宝接收到的参数Map
     * @param publicKey
     *            公钥
     * @return true：验签通过；false：验签不通过
     * @throws Exception
     */
    public static boolean verify(Map<String, Object> params, String publicKey) throws Exception {
        String sign = params.get("sign").toString();
        String content = getSignCheckContent(params);
        log.info("verify sign:{},content:{}", sign, content);
        return generateVerify(content, sign, publicKey);
    }

    public static String generateSign(String content, String privateKey) throws Exception {
        try {
            byte[] encodedKey = privateKey.getBytes();
            encodedKey = Base64.decode(encodedKey);
            PrivateKey priKey = KeyFactory.getInstance(RSA).generatePrivate(new PKCS8EncodedKeySpec(encodedKey));

            Signature signature = Signature.getInstance(SHA_256_WITH_RSA);
            signature.initSign(priKey);
            signature.update(content.getBytes(charset));
            byte[] signed = signature.sign();
            return new String(Base64.encode(signed));
        }
        catch (Exception e) {
            String errorMessage = "签名遭遇异常，请检查私钥格式是否正确。content=" + content + " privateKeySize=" + privateKey.length() + " reason=" + e.getMessage();
            throw new Exception(errorMessage);
        }
    }

    public static boolean generateVerify(String content, String sign, String alipayPublicKey) throws Exception {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            byte[] encodedKey = alipayPublicKey.getBytes();
            encodedKey = Base64.decode(encodedKey);
            PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));

            Signature signature = Signature.getInstance(SHA_256_WITH_RSA);
            signature.initVerify(publicKey);
            signature.update(content.getBytes(charset));
            return signature.verify(Base64.decode(sign.getBytes()));
        }
        catch (Exception e) {
            String errorMessage = "验签遭遇异常，请检查公钥格式或签名是否正确。content=" + content + " sign=" + sign + " publicKey=" + alipayPublicKey + " reason=" + e.getMessage();
            throw new Exception(errorMessage);
        }
    }

    public static String md5(String string) {
        byte[] hash;
        try {
            hash = MessageDigest.getInstance("MD5").digest(string.getBytes(FuYouConstant.charset));
        }
        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Huh, MD5 should be supported?", e);
        }
        catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Huh, UTF-8 should be supported?", e);
        }

        StringBuilder hex = new StringBuilder(hash.length * 2);
        for (byte b : hash) {
            if ((b & 0xFF) < 0x10)
                hex.append("0");
            hex.append(Integer.toHexString(b & 0xFF));
        }
        return hex.toString();
    }



}
